#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

# Author: jiyin@redhat.com
# This is a test run creater.

lappend ::auto_path $::env(HOME)/lib /usr/local/lib /usr/lib64 /usr/lib
package require yaml
package require getOpt 3.0
package require runtestlib 1.1
package require tdom
package require sqlite3
package require md5
namespace import ::getOpt::* ::runtestlib::*

# global var
set prog [file tail $argv0]
array set Opt {}
array set InvalidOpt {}
set Args [list]
set ForwardOpt {}
set OptionList {
  "  Base options:" {
	{help h}      {arg n	help {Print this usage}}
	{pkg name}    {arg y	help {Package/Component name}}
	force  {arg n	help {Add new or overwrite existed testrun items}}
	update {arg n	help {Just overwrite existed test run items}}
	d      {arg n	help {debug mode}}

	f      {arg n   help {just do dynamic filter and output test list}}
	alone  {forward y arg n	help {Submit all tests separately, one case one recipe}}
  }

  "\n  Options for job configuration:" {
	harness			{forward y arg y	help {specify alternative harness, available value: beah|restraint; default: restraint}}
	restraint-git		{forward y arg y	help {specify restraint git repo}}
	restraint		{forward y arg o	help {deprecated} hide y}
	cc			{forward y arg m	help {Notify additional e-mail address on job completion}}
	job-owner		{forward y arg y	help {Submit job on behalf of USERNAME (submitting user must be a submission delegate for job owner)}}
	{wb whiteboard}		{forward y arg y	help {Set the whiteboard for this job}}
	repo			{forward y arg m	help {Configure repo at <URL> in the kickstart for installation}}
	repo-post		{forward y arg m	help {Configure repo at <URL> as part of kickstart %post execution}}
	recipe			{forward y arg n	help {Just generate recipeSet node, internal use for runtest -merge}}
	rwb			{forward y arg y	help {Set the whiteboard for the recipe}}
	{info flag}             {forward y arg y	help {for distinguish repeated run}}
	retention-tag           {forward y arg y        help {e.g: scratch, 60days, 120days, active, active+1, audit}}
	product                 {forward y arg y        help {e.g: cpe:/o:redhat:enterprise_linux:$x:$y}}
	ignore-panic            {forward y arg n        help {Do not abort job if panic message appears on serial console}}
	packages                {forward y arg y	help {e.g: --packages=gcc,screen,wget. or use '/' instead ','}}
  }

  "\n  Options for selecting distro tree(s):" {
	family			{forward y arg m	help {Use latest distro of this FAMILY for job, eg. "RedHatEnterpriseLinux6"}}
	tag			{forward y arg m	help {Use latest distro tagged with TAG, eg. "RTT_ACCEPTED" (default: STABLE)}}
	distro			{forward y arg y	help {Use named distro for job)}}
	variant			{forward y arg y	help {Specify the distro variant}}
	arch			{forward y arg y	help {Specify the distro arch}}
	{dr distrorequire}	{forward y arg m	help {distrorequire -dr=key="value"}}
  }

  "\n  Options for selecting system(s):" {
	{topo}			{forward y arg y	help {Include NUMBER server and client hosts for multi-host test. eg --topo=multiHost.1.1}}
	servers			{forward y arg y	help {Include NUMBER server hosts for multi-host test}}
	clients			{forward y arg y	help {Include NUMBER client hosts for multi-host test}}
	{hr hostrequire}	{forward y arg m	help {Additional <hostRequires/> for job, example: --hostrequire=labcontroller="lab.example.com"}}
	{kv keyvalue}		{forward y arg m	help {Require system with matching legacy key-value, example: --keyvalue=NETWORK=e1000}}
	machine			{forward y arg m	help {Require the machine for job, set comma-separated values for multi-host, example: --machine=SERVER1,CLIENT1}}
	systype			{forward y arg y	help {Require system of TYPE for job (Machine, Prototype, Laptop, ..) default: Machine}}
	ormachine		{forward y arg m	help {Use comma-separated values to set a machine pool, example: --ormachine=HOST1,HOST2,HOST3}}
	random                  {forward y arg n  help {autopick type}}
  }

  "\n  Options for selecting special system(s) of networ-qe:" {
	nay-driver		{link netqe-nic-driver	hide y}
	nic-num			{link netqe-nic-num	hide y}
	nay-nic-driver		{link netqe-nic-driver	hide y}
	nay-nic-num		{link netqe-nic-num	hide y}
	nay-nic-model		{link netqe-nic-model	hide y}
	nay-nic-speed		{link netqe-nic-speed	hide y}
	nay-nic-match		{link netqe-nic-match	hide y}
	nay-nic-unmatch		{link netqe-nic-unmatch hide y}

	netqe-nic-driver	{forward y arg o help ""}
	netqe-nic-num		{forward y arg o help ""}
	netqe-nic-model		{forward y arg o help ""}
	netqe-nic-speed		{forward y arg o help ""}
	netqe-nic-match		{forward y arg o help ""}
	netqe-nic-unmatch	{forward y arg o help {These options together generate the machine pool which match required NIC num/driver/model/speed
				- Refer `parse_netqe_nic_info.sh -h`, which is the engine for the translating.
				  Example: --netqe-nic-driver=e1000e --netqe-nic-num=2
				- Use comma-separated values for different machine pool of multihost
				  Example: --netqe-nic-driver=e1000e,any --netqe-nic-num=2 --netqe-nic-speed=1g }}
  }

  "\n  Options for setting tasks:" {
	task			{forward y arg m	help {Include named task in job, can use multiple times}}
	reboot			{forward y arg y	help {add reboot after each task}}
	{param taskparam}	{forward y arg m	help {Set task params, can use multiple times.
				Use "mh-" prefix to set different value for multihost, example: --param=mh-key=val1,val2}}
	noavc			{forward y arg n	help {alias of --param=AVC_ERROR=+no_avc_check}}
	nvr			{forward y arg m	help {Specify the kernel(Name-Version-Release) to be installed}}
	install			{forward y arg m	help {Install PACKAGE using /distribution/pkginstall, can use multiple times}}
	upstream		{forward y arg o	help {Specify the kernel src git addr to be installed. --upstream=[git://a.b.c/d[#branch[ tag]]]
				e.g: --upstream
				e.g: --upstream=#v4.18-rc1
				e.g: --upstream=git://git.linux-nfs.org/projects/bfields/linux.git#jbf-test-1}}
	upstream-use-clone	{forward y arg o	help {use git-clone instead default git-archive, in case some repo doesn't support git-archive
				- the optional option param is additional option/param of git-clone
				e.g: --upstream-use-clone=--depth=1}}
	upstream-patch		{forward y arg y	help {apply specified patch[es] before compile upstream kernel}}
	upstream-kernel-kasan   {forward y arg n  help {Flag to enable upstream kernel kasan support}}
	Scratch			{forward y arg m  help {Install scratch built package using /distribution/scratchinstall, can use multiple times}}
	scratch			{forward y arg m  help {same as Scratch, but every specified just apply one host, if in multihost mode}}
	dbgk			{forward y arg n	help {Use the debug kernel}}
	gcov			{forward y arg y	help {Enable gcov for coverage data collection, use arg to specify Package, example: --gcov="nfs-utils"}}
	kcov			{forward y arg o	help {Enable kcov for coverage data collection, use arg to specify KDIR, example: --kcov="fs,drivers/net"}}
	kdump			{arg o	help {Enable kdump using /kernel/kdump/setup-nfsdump}}
	nokdump			{arg n	help {disable kdump, some tests can not work with kdump}}
	cmd			{forward y arg m	help {Add /distribution/command before test task}}
	cmdb			{forward y arg m	help {Add /distribution/command before install kernel}}
	leap-second		{forward y arg n	help {Add leap-second task}}
	reserve-if-fail		{forward y arg o	help {Reserve the machine if test fail, specify RESERVETIME with s/m/h/d unit, max amount is 99h}}
	reserve			{forward y arg o	help {Reserve system at the end of the recipe}}
	fips                    {forward y arg o  help {enable fips}}
	abrt                    {forward y arg n  help {enable abrt(insert /distribution/crashes/enable-abrt)}}
  }

  "\n  Options for installation:" {
	{part partition}	{forward y arg m	help {Additional <partitions/> for job, example: --part='fs=xfs name=/mnt/xfs size=10 type=part'}}
	method			{forward y arg y	help {Installation source method (nfs, http, ftp)}}
	ks-meta			{forward y arg m	help {Pass kickstart metadata OPTIONS when generating kickstart}}
	{ks-append ks}		{forward y arg m	help {Specify additional kickstart commands to add to the base kickstart file}}
	ksf			{forward y arg o	help {Similar to --ks-append, but pass the content of a specified file}}
	{k-opts kernel-options}	{forward y arg m	help {Pass OPTIONS to kernel during installation}}
	{k-opts-post kernel-options-post}		{forward y arg m	help {Pass OPTIONS to kernel after installation}}
  }
}

proc Usage {progname {detail n}} {
	puts "Usage0: $progname <distro> <-|testfile...> <--pkg pkgname> \[gen_job_xml options\]"
	puts "Usage1: lstest <argument> | $progname <distro> <--pkg pkgname> \[gen_job_xml options\]"
	puts ""
	if {$detail != "n"} {
		getUsage $::OptionList
	} else {
		puts "* try '$progname -h|less' to get more detail info about options"
	}
}
proc istty {{chann stdin}} {
	dict exists [fconfigure $chann] -mode
}

# _parse_ argument
getOptions $OptionList $::argv Opt InvalidOpt Args ForwardOpt
if [info exist Opt(d)] {
	puts "\[$prog\]: Arguments:{$Args}"
	puts "\[$prog\]: ForwardOpt{$ForwardOpt}"
	parray InvalidOpt
	parray Opt
}

if [info exist Opt(help)] {
	Usage $prog detail
	exit 0
}

if {[llength $Args] < 1} {
	Usage $prog
	exit 1
}

if ![info exist Opt(pkg)] {
	Usage $prog
	exit 1
}

# Get the package/component name
set pkgName $Opt(pkg)

# Get subcmd options
set SubcmdOpt $ForwardOpt
if ![info exist Opt(kdump)] {set Opt(kdump) ""}
if ![info exist Opt(nokdump)] {append SubcmdOpt " --kdump=$Opt(kdump)"}
set TestArgList {}
set Idx [lsearch $Args {--}]
if {$Idx == -1} {
	set TestArgList [lrange $Args 1 end]
} else {
	set TestArgList [lrange $Args 1 [expr $Idx-1]]
	lappend SubcmdOpt {*}[lrange $Args [expr $Idx+1] end]
}

if [info exist Opt(d)] {
	puts "\[$prog\]: SubcmdOpt{$SubcmdOpt}"
}

# Get distro and gset info
set Distro [lindex $Args 0]
set Distro [expandDistro $Distro]   ;#If Distro is short format
set Gset {}
foreach e $SubcmdOpt {
	lappend Gset [regsub {^--} $e {-}]
}

if ![regexp -- {-nvr=} $Gset] {
	set nvr [exec bash -c "vershow ^kernel-\[0-9\] /$Distro$|sed -rn '/^kernel-/{s/\.\[^.\]+\.\[^.\]+$//;p;q}'"]
	if {$nvr == ""} {
		set nvr [exec bash -c "vershow ^kernel-\[0-9\] /[string map {RHEL- {}} $Distro]$|sed -rn '/^kernel-/{s/\.\[^.\]+\.\[^.\]+$//;p;q}'"]
	}
	if {$nvr != ""} {
		set Gset [concat "-info=[regsub kernel- $nvr {}]" $Gset]
	}
}

set Distro_ [concat $Distro $Gset]
if {$Distro_ == ""} {
	puts "\[$prog\]: *WARN*: distro info is nil, something is wrong!"
	Usage $prog
	exit 1
}

# Get the test list
set _TestList [list]
set TestList [list]
if {[llength $TestArgList]==0} {
	if [istty stdin] {
		puts "\[$prog\]: *Warn*: No test list resource specified!"
		Usage $prog
		exit 1
	} else {
		lappend TestArgList -
	}
}
foreach f $TestArgList {
	if {$f in "-"} {
		set fp stdin
		while {-1 != [gets $fp line]} {
			lappend _TestList $line
		}
	} elseif [file isfile $f] {
		if {![catch {set fp [open $f]} err]} {
			while {-1 != [gets $fp line]} {
				if { ! [regexp {^#} $line]} {
					lappend _TestList $line
				}
			}
		}
	}
}

# test filters
foreach test $_TestList {
	#filter: skip unexpected stuff
	set test [string trim $test]
	if {[regexp -- {^#} $test] == 1} continue
	if {$test == "" || $test == "./"} continue
	regsub -line {^-  *} $test {} test

	set tdict [lindex [::yaml::yaml2dict $test] 1]

	if [dict exist $tdict attr disable] {
		if {[dict get $tdict attr disable] ni {no 0}} continue
	}

	#filter: check distro blacklist
	if [dict exist $tdict attr distronotin] {
		set match_black 0
		set blacklist [dict get $tdict attr distronotin]
		foreach pattern {*}$blacklist {
			if [regexp $pattern $Distro] {
				set match_black 1
				break
			}
		}
		if {$match_black == 1} {
			puts stderr "{Filter} match blacklist {$blacklist}: $test"
			continue
		}
	}

	#filter: check distro whitelist
	if [dict exist $tdict attr distroin] {
		set match_white 0
		set whitelist [dict get $tdict attr distroin]
		foreach pattern {*}$whitelist {
			if [regexp $pattern $Distro] {
				set match_white 1
			}
		}
		if {$match_white == 0} {
			puts stderr "{Filter} not match whitelist {$whitelist}: $test"
			continue
		}
	}

	#filter: check arch whitelist and blacklist
	set _arch x86_64
	if {[dict exist $tdict setup] || $SubcmdOpt != ""} {
		set _setup {}
		if [dict exist $tdict setup] {
			set _setup [dict get $tdict setup]
		}
		regexp {.*arch=([^ ]+)} "$_setup $SubcmdOpt" _ignore _arch
	}

	set blacklist {}
	if [dict exist $tdict attr archnotin] {set blacklist [dict get $tdict attr archnotin]}
	if [dict exist $tdict attr noarch] {lappend blacklist [dict get $tdict attr noarch]}
	if {$blacklist != ""} {
		set match_black 0
		foreach pattern {*}$blacklist {
			if [regexp $pattern $_arch] {
				set match_black 1
				break
			}
		}
		if {$match_black == 1} {
			puts stderr "{Filter} match blacklist {$blacklist}: $test"
			continue
		}
	}

	set whitelist {}
	if [dict exist $tdict attr archin] {set whitelist [dict get $tdict attr archin]}
	if [dict exist $tdict attr arch] {lappend whitelist [dict get $tdict attr arch]}
	if {$whitelist != ""} {
		set match_white 0
		foreach pattern {*}$whitelist {
			if [regexp $pattern $_arch] {
				set match_white 1
			}
		}
		if {$match_white == 0} {
			puts stderr "{Filter} not match whitelist {$whitelist}: $test"
			continue
		}
	}

	lappend TestList $test
}
unset _TestList

if [info exist Opt(f)] {
	foreach test $TestList {puts $test}
	exit 0
}

if ![llength $TestList] {
	puts "\[$prog\]: *Warn*: test list is nil, nothing to do, please check your test list file."
	Usage $prog
	exit 1
}

# __main__
puts "\[$prog\]: Create/update run {$Distro_} ..."
# if dbroot dir not exist, create it.
file mkdir [dbroot]
cd [dbroot]
sqlite3 db testrun.db
db timeout 6000

#Create table testrun
db eval {CREATE TABLE if not exists testrun(testid,
	distro_rgset default '',
	jobid default '',
	testStat default '',
	abortedCnt default 0,
	rstat default '',
	res default '',
	taskuri default '',
	resdetail default '',
	comment default '',
	primary key(testid, distro_rgset) )}
#Create table testinfo
db eval {CREATE TABLE if not exists testinfo(testid primary key,
	test default '',
	tier default '1',
	pkgName default '' )}

#Backward compatibility: add column comment, tier
if {"tier" ni [db eval {PRAGMA table_info(testinfo)}]} {
	db eval {ALTER TABLE testinfo ADD tier default('')}
}
if {"comment" ni [db eval {PRAGMA table_info(testrun)}]} {
	db eval {ALTER TABLE testrun ADD comment default('')}
}

#Backward compatibility: rename column
if {"testplanName" in [db eval {PRAGMA table_info(testinfo)}]} {
	db eval {
		ALTER TABLE testinfo RENAME TO testinfo_old;
		CREATE TABLE testinfo(testid primary key, test default '', tier default '1', pkgName default '' );
		INSERT INTO testinfo(testid, test, pkgName)
			SELECT testid, test, tier, testplanName FROM testinfo_old;
		DROP TABLE testinfo_old
	}
}

# insert transaction
db transaction {
	foreach test $TestList {
		set tdict [::yaml::yaml2dict $test]
		set tname [lindex $tdict 0]
		set tdict [lindex $tdict 1]

		set tier tier1
		if [dict exist $tdict attr tier] {
			set tier [dict exist $tdict attr tier]
		}
		set param {}
		if [dict exist $tdict param] {
			set param [dict get $tdict param]
		}
		set setup {}
		if [dict exist $tdict setup] {
			set setup [dict get $tdict setup]
		}
		set key "$tname $param $setup"
		set testid [::md5::md5 -hex [string trim $key]]

		# verify the arch
		regexp -- {-arch=([a-z0-9]+)} "$setup $SubcmdOpt" _ignore arch
		if {[info exist arch] && [regexp {RHEL-7} $Distro_] && [regexp {i386} $arch]} {
			continue
		}

		set testInsert {INSERT OR REPLACE INTO testinfo (testid, test, tier, pkgName) VALUES($testid, $test, $tier, $pkgName)}
		db eval $testInsert

		if [info exist Opt(force)] {
			set testrunInsert {INSERT OR REPLACE INTO testrun (testid, distro_rgset, abortedCnt, res) VALUES($testid, $Distro_, 0, '-')}
		} elseif [info exist Opt(update)] {
			set testrunInsert {
				UPDATE OR IGNORE testrun
				set jobid='', testStat='', res='o', rstat='', taskuri='', abortedCnt=0, resdetail=''
				WHERE testid = $testid and distro_rgset = $Distro_
			}
		} else {
			set testrunInsert {INSERT OR IGNORE INTO testrun (testid, distro_rgset, abortedCnt, res) VALUES($testid, $Distro_, 0, '-')}
		}
		db eval $testrunInsert
	}
}

# create cron task for bkr-autorun-monitor
exec bash -c {
	crontab -l 2>/dev/null|grep -q "/usr/local/bin/bkr-autorun-monitor" || {
		(
		 echo "SHELL=/bin/bash"
		 echo "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
		 crontab -l|sed -r '/^(SHELL|PATH)=/d'
		 echo "05,35 *  *  *  *    /usr/local/bin/bkr-autorun-monitor &>/tmp/bkr-autorun-$USER.log;"
		) | crontab -
	}
	:
}

